<?php
/**
 * 文件上传方法
 * @author yumeixin@star-net.cn
 * @copyright  ktvme. All Rights Reserved.
 * @version $Id: CurlUtil.php 632 2018-05-07 06:13:40Z chendanming $
 */

namespace curl;

use think\Session;
use think\Log;
/**
 *
 * $Id:Curl.class.php
 *
 * CURL HTTP工具类
 *
 * 支持以下功能：
 * 1：支持ssl连接和proxy代理连接
 * 2: 对cookie的自动支持
 * 3: 简单的GET/POST常规操作
 * 4: 支持单个文件上传或同字段的多文件上传,支持相对路径或绝对路径.
 * 5: 支持返回发送请求前和请求后所有的服务器信息和服务器Header信息
 * 6: 自动支持lighttpd服务器
 * 7: 支持自动设置 REFERER 引用页
 * 8: 自动支持服务器301跳转或重写问题
 * 9: 其它可选项,如自定义端口，超时时间，USERAGENT，Gzip压缩等.
 *
 */
defined('KMUSERAGENT') ? KMUSERAGENT : define('KMUSERAGENT', 'kmads-admin');

class CurlUtil {

    private $ch   = null;    //CURL句柄
    private $info = array();  //CURL执行前后所设置或服务器端返回的信息
    private $_curlGetInfo = '';
    //CURL SETOPT 信息
    private $setopt = array(
        'port'          => 80,
        //访问的端口,http默认是 80
        'userAgent'     => KMUSERAGENT,
        //客户端 USERAGENT,如:"Mozilla/4.0",为空则使用用户的浏览器
        'timeOut'       => 15,
        //连接超时时间
        'useCookie'     => false,
        //是否使用 COOKIE 建议打开，因为一般网站都会用到
        'ssl'           => false,
        //是否支持SSL
        'gzip'          => true,
        //客户端是否支持 gzip压缩
        'proxy'         => false,
        //是否使用代理
        'proxyType'     => 'HTTP',
        //代理类型,可选择 HTTP 或 SOCKS5
        'proxyHost'     => '192.168.9.25',
        //代理的主机地址
        'proxyPort'     => 8081,
        //代理主机的端口
        'proxyAuth'     => false,
        //代理是否要身份认证(HTTP方式时)
        'proxyAuthType' => 'BASIC',
        //认证的方式.可选择 BASIC 或 NTLM 方式
        'proxyAuthUser' => 'user',
        //认证的用户名
        'proxyAuthPwd'  => 'password',
        //认证的密码
        'isUserAgent'   => false,
    );

    const ALL = -2147483647;

    private static $instanse      = null;
    private static $strRequestID  = '11110000';
    private static $nRequestLevel = self::ALL;
    private static $isWriteLog    = true;

    public function setConfig($setopt = array()) {
        $this->setopt = array_merge($this->setopt, $setopt);  //合并用户的设置和系统的默认设置
    }

    public static function getInstance() {
        if(!function_exists('curl_init')) {
            return false;
        }
        if(is_null(self::$instanse)) {
            self::$instanse = new CurlUtil();
        }

        return self::$instanse;
    }

    public static function getInstanceByConfig($setopt = array()) {
        if(!function_exists('curl_init')) {
            return false;
        }
        if(is_null(self::$instanse)) {
            self::$instanse = new CurlUtil($setopt);
        }

        return self::$instanse;
    }

    /**
     * 构造函数
     *
     * @param array $setopt :请参考 private $setopt 来设置
     */
    private function __construct($setopt = array()) {

        if(!empty($setopt)) {
            $this->setopt = array_merge($this->setopt, $setopt);  //合并用户的设置和系统的默认设置
        }


        $this->ch = curl_init();  //初始化
        //使用代理
        if($this->setopt['proxy']) {
            $proxyType = $this->setopt['proxyType'] == 'HTTP' ? CURLPROXY_HTTP : CURLPROXY_SOCKS5;
            curl_setopt($this->ch, CURLOPT_PROXYTYPE, $proxyType);
            curl_setopt($this->ch, CURLOPT_PROXY, $this->setopt['proxyHost']);
            curl_setopt($this->ch, CURLOPT_PROXYPORT, $this->setopt['proxyPort']);

            //代理要认证
            if($this->setopt['proxyAuth']) {
                $proxyAuthType = $this->setopt['proxyAuthType'] == 'BASIC' ? CURLAUTH_BASIC : CURLAUTH_NTLM;
                curl_setopt($this->ch, CURLOPT_PROXYAUTH, $proxyAuthType);
                $user = "[{$this->setopt['proxyAuthUser']}]:[{$this->setopt['proxyAuthPwd']}]";
                curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $user);
            }
        }

        curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true); //启用时会将服务器服务器返回的“Location:”放在header中递归的返回给服务器
        //打开的支持SSL
        if($this->setopt['ssl']) {
            curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false); //不对认证证书来源的检查
            curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); //从证书中检查SSL加密算法是否存在
            $this->setopt['port'] = 443;
        }
        if($this->setopt['port'] != 80) {//默认端口不需要设置，否则url带端口号过不去
            curl_setopt($this->ch, CURLOPT_PORT, $this->setopt['port']); //设置CURL连接的端口
        }

        $header[] = 'Expect:'; //设置http头,支持lighttpd服务器的访问
        $header[] = 'RequestID:' . self::$strRequestID;
        $header[] = 'RequestLevel:' . self::$nRequestLevel;
        curl_setopt($this->ch, CURLOPT_HTTPHEADER, $header);
        if ($this->setopt['isUserAgent']) {
            $userAgent = $this->setopt['userAgent'] ? $this->setopt['userAgent'] : $_SERVER['HTTP_USER_AGENT'];  //设置 HTTP USERAGENT
            curl_setopt($this->ch, CURLOPT_USERAGENT, $userAgent);
        }
        curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->setopt['timeOut']); //设置连接等待时间,0不等待
        curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->setopt['timeOut']);   //设置curl允许执行的最长秒数
        //设置客户端是否支持 gzip压缩
        if($this->setopt['gzip']) {
            curl_setopt($this->ch, CURLOPT_ENCODING, 'gzip');
        }
        //是否使用到COOKIE
        if($this->setopt['useCookie']) {
            $cookfile = tempnam(sys_get_temp_dir(), 'cuk'); //生成存放临时COOKIE的文件(要绝对路径)
            $this->setCookie($cookfile);
        }
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); //获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($this->ch, CURLOPT_BINARYTRANSFER, true);
    }

    public function setCookie($p_strCookieFile) {
        curl_setopt($this->ch, CURLOPT_COOKIEJAR, $p_strCookieFile);
        curl_setopt($this->ch, CURLOPT_COOKIEFILE, $p_strCookieFile);
    }

    /**
     * 以 GET 方式执行请求
     *
     * @param string $url : 请求的URL
     *
     * @param array $params ：请求的参数,格式如: array('id'=>10,'name'=>'yuanwei')
     *
     * @param array $referer :引用页面,为空时自动设置,如果服务器有对这个控制的话则一定要设置的.
     *
     * @return 错误返回:false 正确返回:结果内容
     */
    public function get($url, $params = array(), $referer = '') {
        return $this->_request('GET', $url, $params, array(), $referer);
    }

    /**
     * 以 POST 方式执行请求
     *
     * @param string $url :请求的URL
     *
     * @param array $params ：请求的参数,格式如: array('id'=>10,'name'=>'yuanwei')
     *
     * @param array $uploadFile :上传的文件,支持相对路径,格式如下:
     * 单个文件上传:array('img1'=>'./file/a.jpg'),同字段多个文件上传:array('img'=>array('./file/a.jpg','./file/b.jpg'))
     *
     * @param array $referer :引用页面,引用页面,为空时自动设置,如果服务器有对这个控制的话则一定要设置的.
     *
     * @return 错误返回:false 正确返回:结果内容
     */
    public function post($url, $params = array(), $uploadFile = array(), $referer = '') {
        return $this->_request('POST', $url, $params, $uploadFile, $referer);
    }

    /* public function postCusterm($url,$params=array(), $referer=''){
      return $this->_request('POST', $url, $params, $referer);
      } */

    /**
     * 以POST方式执行请求（含Header）
     * @param $url
     * @param array $params
     * @param array $header
     * @return 错误返回
     */
    public function postWithHeader($url, $params = array(), $header = array(), $uploadFile = array(), $referer = '') {

        if(!empty($header)) {
            $headerArr[] = 'RequestID:' . self::$strRequestID;
            $headerArr[] = 'RequestLevel:' . self::$nRequestLevel;
            foreach($header as $n => $v) {
                $headerArr[] = $n . ':' . $v;
            }
            curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headerArr);
        }

        return $this->_request('POST', $url, $params, $uploadFile, $referer);
    }

    /**
     * 以application/x-www-form-urlencode的格式发送post请求
     * @param $url
     * @param $data
     * @return 错误返回
     */
    public function postForm($url, $data) {
        $header = ['Content-Type' => 'application/x-www-form-urlencode'];

        return $this->postWithHeader($url, $data, $header);
    }


    /**
     * 以application/json的格式发送post请求
     * @param $url
     * @param $data
     * @return 错误返回
     */
    public function postJson($url, $data) {
        $header = [
            'Content-Type'   => 'application/json; charset=utf-8',
            'Content-Length' => strlen($data)
        ];

        return $this->postWithHeader($url, $data, $header);
    }


    /**
     * 得到错误信息
     *
     * @return string
     */
    public function error() {
        return curl_error($this->ch);
    }

    /**
     * 得到错误代码
     *
     * @return int
     */
    public function errno() {
        return curl_errno($this->ch);
    }

    /**
     * 得到发送请求前和请求后所有的服务器信息和服务器Header信息:
     * [before] ：请求前所设置的信息
     * [after] :请求后所有的服务器信息
     * [header] :服务器Header报文信息
     *
     * @return array
     */
    public function getInfo() {
        return $this->info;
    }

    /**
     * 析构函数
     *
     */
    public function __destruct() {
        curl_close($this->ch);
    }

    /**
     * 执行请求
     *
     * @param string $method :HTTP请求方式
     *
     * @param string $url :请求的URL
     *
     * @param array $params ：请求的参数
     *
     * @param array $uploadFile :上传的文件(只有POST时才生效)
     *
     * @param array $referer :引用页面
     *
     * @return 错误返回:false 正确返回:结果内容
     */
    private function _request($method, $url, $params = array(), $uploadFile = array(), $referer = '') {
        if(strpos($url, 'https', 0) === 0) {
            curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false); //不对认证证书来源的检查
            curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); //从证书中检查SSL加密算法是否存在
            $this->setopt['port'] = 443;
        }
        //如果是以GET方式请求则要连接到URL后面
        if($method == 'GET') {
            $url = $this->_parseUrl($url, $params);
        }
        curl_setopt($this->ch, CURLOPT_URL, $url); //设置请求的URL
        //如果是POST
        $postData = array();
        if($method == 'POST') {
            curl_setopt($this->ch, CURLOPT_POST, true); //发送一个常规的POST请求，类型为：application/x-www-form-urlencoded
            //$postData = $this->_parsmEncode($params,false);	//设置POST字段值
            $postData = $params;
            //如果有上传文件
            if($uploadFile) {
                foreach($uploadFile as $key => $file) {
                    if(is_array($file)) {
                        $n = 0;
                        foreach($file as $f) {
                            $postData[$key . '[' . $n++ . ']'] = '@' . realpath($f); //文件必需是绝对路径
                        }
                    } else {
                        $postData[$key] = new \CURLFile(realpath($file));
                    }
                }
            }
            curl_setopt($this->ch, CURLOPT_POSTFIELDS, $postData);
        }

        //设置了引用页,否则自动设置
        if($referer) {
            curl_setopt($this->ch, CURLOPT_REFERER, $referer);
        } else {
            curl_setopt($this->ch, CURLOPT_AUTOREFERER, true);
        }
        $result = curl_exec($this->ch); /**/
        $curlGetInfo = curl_getinfo($this->ch);
        $this->_curlGetInfo = array('getinfo'=>$curlGetInfo);

        $requestInfo = is_array($params)?array_merge($params,['url'=>$url,'method'=>$method]):['param'=>$params,'url'=>$url,'method'=>$method];
        //如果请求成功
        $mxResult = false;
        if($this->errno() == 0) { //&& $this->info['after']['http_code'] == 200
            $mxResult = $this->_jsonToArray($result);
        } else {
            $mxResult = false;
        }

        //写log
        if ( self::$isWriteLog ) {
            $this->_addCurlInfoLog($requestInfo, $mxResult, $this->_curlGetInfo);
        }
        if($mxResult && !empty($mxResult)) {
            return $mxResult;
        } else {
            return [
                'error'   => 2,
                'message' => 'request curl return result format error[' . $url . ']'
            ];
        }
    }

    /**
     * 返回解析后的URL，GET方式时会用到
     *
     * @param string $url :URL
     * @param array $params :加在URL后的参数
     * @return string
     */
    private function _parseUrl($url, $params) {
        $fieldStr = $this->_parsmEncode($params);
        if($fieldStr) {
            $url .= strstr($url, '?') === false ? '?' : '&';
            $url .= $fieldStr;
        }

        return $url;
    }

    /**
     * 对参数进行ENCODE编码
     *
     * @param array $params :参数
     * @param bool $isRetStr : true：以字符串返回 false:以数组返回
     * @return string || array
     */
    private function _parsmEncode($params, $isRetStr = true) {
        $fieldStr = '';
        $spr      = '';
        $result   = array();
        foreach($params as $key => $value) {
            $value = urlencode($value);
            $fieldStr .= $spr . $key . '=' . $value;
            $spr          = '&';
            $result[$key] = $value;
        }

        return $isRetStr ? $fieldStr : $result;
    }

    /**
     * 上传文件
     * @param array $p_arrData
     * @param string $p_strRealPath 文件路径或者为array(),当上传种子时
     * @param string $p_strUrl
     * @return Ambigous <错误返回:false, boolean, mixed>
     */
    public function upload($p_arrData, $p_strRealPath, $p_strUrl) {
        $arrData = $p_arrData;
        if(filesize($p_strRealPath['file']) > 400 * 1024) {

        }
        $arrData['chunks'] = 1;
        $arrData['chunk']  = 0;

        return $this->post($p_strUrl, $arrData, $p_strRealPath);
    }

    /**
     * 获取curl返回头信息
     * @return mixed
     * @author  xurongfeng
     */
    public function curlGetInfo() {
        return curl_getinfo($this->ch);
    }

    /**
     * curl下载文件
     * @param $url
     * @param $dir
     * @param $filename
     * @author  xurongfeng
     */
    public static function down($url, $dir, $filename) {
        $curl  = CurlUtil::getInstance();
        $mxRes = $curl->get($url);
        //文件大小
        if(!empty($dir)) {
            if(!file_exists($dir)) {
                $res = mkdir($dir);
            }
        }
        $fp2 = @fopen($dir . $filename, 'w');
        fwrite($fp2, $mxRes);
        fclose($fp2);
    }

    /**
     * 获取令牌
     * @param array $p_arrInput
     * @return array
     */
    public static function getAuthSignStr($p_arrInput) {
        $appid               = config('params.CurlAppId');
        $appsecret           = config('params.CurlAppSecret');
        $p_arrInput['appid'] = $appid;
        $arrTrans            = array_change_key_case(array_filter($p_arrInput, function($arrInput) {
            return $arrInput !== '';
        }), CASE_LOWER);
        $strTrans            = '';
        // 按key值升序排序
        if(ksort($arrTrans)) {
            foreach($arrTrans as $key => $value) {
                if($key == 'sign') {
                    continue;
                }
                $strTrans .= $key . $value;
            }
            $strSign = md5($strTrans . $appsecret);
        }

        return array(
            'appid' => $appid,
            'sign'  => strtoupper($strSign)
        );
    }

    /**
     * 数组key=>val转get请求的参数形式
     * @param array $p_params
     * @return string
     */
    public static function arrayToGetParam($p_params) {
        $arrGetParam = array();
        foreach($p_params as $key => $val) {
            $arrGetParam[] = $key . '=' . $val;
        }
        $strGetParam = implode('&', $arrGetParam);

        return $strGetParam;
    }

    /**
     * curl接口请求
     * @param string $p_type HTTP请求方式
     * @param string $p_apiUrl 请求完整地址
     * @param array $p_params 请求参数
     * @param bool $p_IsSign 是否进行令牌验证
     * @return string
     */
    public static function curlConnection($p_type, $p_apiUrl, $p_params, $p_IsSign = false) {
        $objCurl = self::getInstance();
        //是否调用appid和签名
        if($p_IsSign == true) {
            $sign     = $objCurl->getAuthSignStr($p_params);
            $p_params = array_merge($p_params, $sign);
        }
        if($p_type == 'get') {
            $curlStr  = $objCurl->arrayToGetParam($p_params);
            $curlData = $objCurl->get($p_apiUrl . '?' . $curlStr);
        } else if($p_type == 'post') {
            $curlData = $objCurl->post($p_apiUrl, $p_params);
        }

        return $curlData;
    }

    public function setHeader($header) {
        if(!empty($header)) {
            $headerArr[] = 'RequestID:' . self::$strRequestID;
            $headerArr[] = 'RequestLevel:' . self::$nRequestLevel;
            foreach($header as $n => $v) {
                $headerArr[] = $n . ':' . $v;
            }
            curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headerArr);
        }
    }

    /**
     * curl接口请求,返回值进行转码与错误码处理
     * @param string $p_type HTTP请求方式
     * @param string $p_apiUrl 请求完整地址
     * @param array $p_params 请求参数
     * @param bool $p_isSign 是否进行令牌验证
     * @param string $p_decode 转码格式
     * @return string
     */
    public static function decodeCurlConnection($p_type, $p_apiUrl, $p_params, $p_isSign = false, $p_decode = 'json') {
        if($p_decode == 'json') {
            Session::set('errorMessage', []);
            $curlResult       = self::curlConnection($p_type, $p_apiUrl, $p_params, $p_isSign);
            $curlResultDecode = json_decode($curlResult);
        }
        if(isset($curlResultDecode->errno)) {
            if($curlResultDecode->errno == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode->error]);
            }
        } else if(isset($curlResultDecode->errorcode)) {
            if($curlResultDecode->errorcode == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode->errormessage]);
            }
        } else if(isset($curlResultDecode->status)) {
            if($curlResultDecode->status == 200) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode->message]);
            }
        } else if(isset($curlResultDecode->error)) {
            if($curlResultDecode->error == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode->message]);
            }
        } else
            $error = '0101002';

        //写入错误日志
        return $error;
    }

    /**
     * curl接口请求,返回值进行转码与错误码处理,返回数据统一为数组
     * @param string $p_type HTTP请求方式
     * @param string $p_apiUrl 请求完整地址
     * @param array $p_params 请求参数
     * @param bool $p_isSign 是否进行令牌验证
     * @param string $p_decode 转码格式
     * @return string
     */
    public static function decodeArrayCurlConnection($p_type, $p_apiUrl, $p_params = [], $p_isSign = false, $p_decode = 'json') {
        if($p_decode == 'json') {
            Session::set('errorMessage', []);
            $curlResult       = self::curlConnection($p_type, $p_apiUrl, $p_params, $p_isSign);
            $curlResultDecode = json_decode($curlResult, true);
        }
        if(isset($curlResultDecode['errno'])) {
            if($curlResultDecode['errno'] == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode['error']]);
            }
        } else if(isset($curlResultDecode['errorcode'])) {
            if($curlResultDecode['errorcode'] == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode['errormessage']]);
            }
        } else if(isset($curlResultDecode['status'])) {
            if($curlResultDecode['status'] == 200) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode['message']]);
            }
        } else if(isset($curlResultDecode['error'])) {
            if($curlResultDecode['error'] == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode['message']]);
            }
        } else if(isset($curlResultDecode['ret'])) {
            if($curlResultDecode['ret'] == 0) {
                return $curlResultDecode;
            } else {
                $error = '0101001';
                Session::set('errorMessage', ['0101001' => $curlResultDecode['msg']]);
            }
        } else
            $error = '0101002';

        //写入错误日志
        return $error;
    }


    /**
     * curl接口请求,返回数据统一为数组
     * @param string $p_type HTTP请求方式
     * @param string $p_apiUrl 请求完整地址
     * @param array $p_params 请求参数
     * @param bool $p_isSign 是否进行令牌验证
     */
    public static function getDataByCurlConnection($p_type, $p_apiUrl, $p_params = [], $p_isSign = false) {
        $curlResult = self::curlConnection($p_type, $p_apiUrl, $p_params, $p_isSign);

        return DataUtil::jsonToArray($curlResult);
    }

    /**
     * 20180411 是否写详细的curl请求log
     * @param $has
     */
    public function setIsWriteCurlLog($has){
        self::$isWriteLog = $has;
    }

    private function _getCurlGetInfo($p_curlGetInfo) {
        return [
            'httpcode'      => $p_curlGetInfo['http_code'],
            'total'         => $p_curlGetInfo['total_time'],
            'namelookup'    => $p_curlGetInfo['namelookup_time'],
            'connect'       => $p_curlGetInfo['connect_time'],
            'pretransfer'   => $p_curlGetInfo['pretransfer_time'],
            'size_dl'       => $p_curlGetInfo['size_download'],
            'speed_dl'      => $p_curlGetInfo['speed_download'],
            'starttransfer' => $p_curlGetInfo['starttransfer_time']
        ];
    }

    private function _addCurlInfoLog($requestInfo, $responseInfo, $curlGetInfo) {

        $curl_request = json_encode($requestInfo, JSON_UNESCAPED_UNICODE);
        $curl_response = json_encode($responseInfo, JSON_UNESCAPED_UNICODE);

        $curlGetInfo = $curlGetInfo['getinfo'];
        $curlInfo    = $this->_getCurlGetInfo($curlGetInfo);
        $curlTime    = $curlGetInfo['total_time'];

        $request_time_float = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : "";

        $curl_time    = $curlTime . '|' . number_format((microtime(true) - $request_time_float), 6);
        $curl_getinfo = json_encode($curlInfo);

        Log::record($curl_request, 'elk_more_info', 'curl_request');
        Log::record($curl_response, 'elk_more_info', 'curl_response');
        Log::record($curl_time, 'elk_more_info', 'curl_time');
        Log::record($curl_getinfo, 'elk_more_info', 'curl_getinfo');

    }

    /**
     * json转array
     * @param $json json格式内容
     * @return array 解析后的数组
     */
    private function _jsonToArray($json) {
        $arr = [];

        $tmp = $json;
        if(!is_object($tmp)) {
            $tmp = json_decode($json, true);
        }
        if($tmp && is_array($tmp)) {
            foreach($tmp as $key => $val) {
                if(is_object($val)) $arr[$key] = self::jsonToArray($val); //判断类型是不是object
                else $arr[$key] = $val;
            }
        }

        return count($arr)>0?$arr:$json;
    }
}
